package jbitarrays;

import java.io.Closeable;
import java.io.File;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;

import jbitarrays.api.CrawlerReader;
import jbitarrays.api.CrawlerWriter;
import jbitarrays.api.InfiniteBitArray;

/**
 * Factory for implementations of the {@link InfiniteBitArray},
 * {@link CrawlerReader} and {@link CrawlerWriter} interfaces provided by this
 * library
 * 
 * @since 1.0
 */
public final class JBitArrays {
	private JBitArrays() {}

	/**
	 * An {@link InfiniteBitArray} backed by heap memory.
	 * 
	 * @param expectedNumberOfBits
	 *            a good guess at the number of bits you'll use <i>may</i> help
	 *            prevent resizing in the implementation.
	 * @return a mutable view of the memory
	 * @since 1.0
	 */
	public static @Nonnull InfiniteBitArray memoryAsBitArray(@Nonnegative int expectedNumberOfBits) {
		return new InfiniteBitSet(expectedNumberOfBits);
	}

	/**
	 * An {@link InfiniteBitArray} backed by a {@link File}.
	 * 
	 * @param file
	 *            a readable & writeable existing file (not a directory)
	 * @param bufferSizeBytes
	 *            the most amount of RAM to use (in bytes) to help read this
	 *            file. This could be via memory-mapping the file if the
	 *            operating system supports it.
	 * @return a mutable view of the file that is {@link Closeable}
	 * @since 1.0
	 */
	public static @Nonnull InfiniteBitArray fileAsBitArray(@Nonnull File file, @Nonnegative int bufferSizeBytes) {
		return new InfiniteBitFile(file, bufferSizeBytes, Math.min(bufferSizeBytes / 10, Math.max(bufferSizeBytes / 100, 1000)));
	}

	/**
	 * Start reading a contiguous sequence of bits from an
	 * {@link InfiniteBitArray}
	 * 
	 * @param array
	 *            the {@link InfiniteBitArray} to read from
	 * @param bitIndexFrom
	 *            the index of the bit in "array" that will be read first
	 * @since 1.0
	 */
	public static @Nonnull CrawlerReader startReading(@Nonnull InfiniteBitArray array, @Nonnegative int bitIndexFrom) {
		return new DefaultCrawlerReader(array, bitIndexFrom);
	}

	/**
	 * Start writing a contiguous sequence of bits to an
	 * {@link InfiniteBitArray}
	 * 
	 * @param array
	 *            the {@link InfiniteBitArray} to write to
	 * @param bitIndexFrom
	 *            the index of the bit in "array" that will be written first
	 * @since 1.0
	 */
	public static @Nonnull CrawlerWriter startWriting(@Nonnull InfiniteBitArray array, @Nonnegative int bitIndexFrom) {
		return new DefaultCrawlerWriter(array, bitIndexFrom);
	}

	/**
	 * Decorates a {@link CrawlerWriter} so that the returned
	 * {@link CrawlerWriter} only writes sequences of "writeLength" bits,
	 * skipping "skipLength" bits between each sequence. This can be useful if
	 * you want to interleave two sets of data in a single
	 * {@link InfiniteBitArray}.
	 * 
	 * @param decorated
	 *            the {@link CrawlerWriter} to be decorated. Advancing the
	 *            returned {@link CrawlerWriter} will advance this
	 *            {@link CrawlerWriter}.
	 * @param writeLength
	 *            the number of bits the returned {@link CrawlerWriter} will
	 *            write contiguously.
	 * @param skipLength
	 *            the number of bits to skip between each contiguous sequence of
	 *            "writeLength" bits.
	 * @throws IllegalArgumentException
	 *             if "writeLength" is zero or less as the {@link CrawlerWriter}
	 *             will never write any bits in this case.
	 * @throws IllegalArgumentException
	 *             if "skipLength" is less than zero.
	 * @since 1.0
	 * @see #skipReader(CrawlerReader, int, int)
	 */
	public static @Nonnull CrawlerWriter skipWriter(@Nonnull CrawlerWriter decorated, @Nonnegative int writeLength, @Nonnegative int skipLength) {
		return new PeriodicCrawlerWriter(decorated, writeLength, skipLength);
	}

	/**
	 * Decorates a {@link CrawlerReader} so that the returned
	 * {@link CrawlerReader} only reads sequences of "readLength" bits, skipping
	 * "skipLength" bits between each sequence. This can be useful if you want
	 * to interleave two sets of data in a single {@link InfiniteBitArray}.
	 * 
	 * @param decorated
	 *            the {@link CrawlerReader} to be decorated. Advancing the
	 *            returned {@link CrawlerReader} will advance this
	 *            {@link CrawlerReader}.
	 * @param readLength
	 *            the number of bits the returned {@link CrawlerReader} will
	 *            read contiguously.
	 * @param skipLength
	 *            the number of bits to skip between each contiguous sequence of
	 *            "readLength" bits.
	 * @throws IllegalArgumentException
	 *             if "readLength" is zero or less as the {@link CrawlerReader}
	 *             will never read any bits in this case.
	 * @throws IllegalArgumentException
	 *             if "skipLength" is less than zero.
	 * @since 1.0
	 * @see #skipWriter(CrawlerWriter, int, int)
	 */
	public static @Nonnull CrawlerReader skipReader(@Nonnull CrawlerReader decorated, @Nonnegative int readLength, @Nonnegative int skipLength) {
		return new PeriodicCrawlerReader(decorated, readLength, skipLength);
	}

	/**
	 * A {@link CrawlerReader} that encodes bits using a run-length encoding
	 * scheme before writing them to the decorated {@link CrawlerReader}. The
	 * encoding writes the number of consecutive ones or zeros as a signed
	 * integer of {@code bitsForNumber} bits; if there are more consecutive ones
	 * or zeros than can be represented in {@code bitsForNumber} bits "dummy"
	 * runs of zero length will be inserted. This encoding means that a
	 * different number of bits could be written to the decorated
	 * {@link CrawlerReader} that are written to the returned
	 * {@link CrawlerReader}, so <b>writes to a
	 * {@link #runLengthEncodingWriter(CrawlerWriter, int)} may corrupt data at
	 * indices after the one that is written to</b>, and reads to areas near
	 * areas being written <b>may return incorrect data</b>.
	 * 
	 * @param decorated
	 *            the {@link CrawlerReader} to be decorated. Advancing the
	 *            returned {@link CrawlerReader} <i>may</i> advance this
	 *            {@link CrawlerReader}.
	 * @throws IllegalArgumentException
	 *             if {@code bitsForNumber < 0} or
	 *             {@code bitsForNumber > Integer#SIZE}
	 * @since 1.0
	 * @see #runLengthEncodingWriter(CrawlerWriter, int)
	 * @see "http://en.wikipedia.org/wiki/Run-length_encoding"
	 */
	public static @Nonnegative CrawlerReader runLengthEncodingReader(@Nonnull CrawlerReader decorated, @Nonnegative int bitsForNumber) {
		return new RunLengthEncodingReader(decorated, bitsForNumber);
	}

	/**
	 * A {@link CrawlerWriter} that encodes bits using a run-length encoding
	 * scheme before writing them to the decorated {@link CrawlerWriter}. The
	 * encoding writes the number of consecutive ones or zeros as a signed
	 * integer of {@code bitsForNumber} bits; if there are more consecutive ones
	 * or zeros than can be represented in {@code bitsForNumber} bits "dummy"
	 * runs of zero length will be inserted. This encoding means that a
	 * different number of bits could be written to the decorated
	 * {@link CrawlerReader} that are written to the returned
	 * {@link CrawlerReader}, so <b>writes to a
	 * {@link #runLengthEncodingWriter(CrawlerWriter, int)} may corrupt data at
	 * indices after the one that is written to</b>, and reads to areas near
	 * areas being written <b>may return incorrect data</b>.
	 * 
	 * @param decorated
	 *            the {@link CrawlerWriter} to be decorated. Advancing the
	 *            returned {@link CrawlerWriter} <i>may</i> advance this
	 *            {@link CrawlerWriter}.
	 * @throws IllegalArgumentException
	 *             if {@code bitsForNumber < 0} or
	 *             {@code bitsForNumber > Integer#SIZE}
	 * @since 1.0
	 * @see #runLengthEncodingReader(CrawlerReader, int)
	 * @see "http://en.wikipedia.org/wiki/Run-length_encoding"
	 */
	public static @Nonnegative CrawlerWriter runLengthEncodingWriter(@Nonnull CrawlerWriter decorated, @Nonnegative int bitsForNumber) {
		return new RunLengthEncodingWriter(decorated, bitsForNumber);
	}
}
